<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>锄大地游戏页</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font: 13px Helvetica, Arial; }
        form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
        form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
        form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
        #messages { list-style-type: none; margin: 0; padding: 0; }
        #messages li { padding: 5px 10px; }
        #messages li:nth-child(odd) { background: #eee; }
    </style>
    <link rel="stylesheet" type="text/css" href='css/style.css'/>
</head>
<script src="lib/socket.io.js"></script>
<script src="lib/jquery-1.11.1.js"></script>
<script src="js/component.js"></script>
<script src="js/gameUtils.js"></script>
<body>
    <button onclick="playRecord('room1000_2021-11-30T03:06:57.965Z')">回放</button>
    <div class="chudadiTable">
    </div>
</body>
<script>
    // 获取链接参数
    function getUrlParam(name){
        var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if (r!=null) return unescape(r[2]); return null;
    }
    // 可执行动作码
    const ACTION_CODE = {
        Play: 4, // 打牌
        Follow: 2, // 跟牌
        Pass: 1, // 过
        None: 0 // 无动作
    };

    // 暂时从链接获取用户名
    var userName = getUrlParam('userName') || 'player1';
    // 回放
    var recordMap = {}
    // 模拟4个玩家
    var socket = null, 
        currentPlayerId = "", state = "", gameData = null;
    var lastGameData = null; // 缓存上一次接收的gameData
    var jc = new JsonCompressor; // json压缩器
    socket = io({path: '/mahjong/socket.io'});
    socket.on('news', onNews);
    socket.on('connect', () => login(userName));

    function reset() {
        currentPlayerId = "", state = "", gameData = null;
        lastGameData = null;
        jc = new JsonCompressor;
    }
    function onNews(msg) {
        let originLength = msg.length;
        console.log(`收到信息：${msg}`);
        let uncompressMsg = jc.fromCompressString(msg);
        jc.importUncompressMap(uncompressMsg.uncompressMap);
        delete uncompressMsg.uncompressMap;
        let json = jc.uncompress(uncompressMsg);
        let realMsgLength = JSON.stringify(json).length;
        console.log(`信息解压：${JSON.stringify(json)}`);
        console.log(`收到信息长度：${originLength}，解析后信息长度：${realMsgLength}，压缩效率（(解析后信息长度-收到信息长度)/解析后信息长度）：${((realMsgLength-originLength)/realMsgLength*100).toFixed(2)}%`);
        // 合并/覆盖 新状态数据
        currentPlayerId = json.tableData.currentPlayerId;
        state = json.tableData.state;
        if (json.incremental && lastGameData) { // 增量更新
            // playerData增量更新
            for (let playerId in json.playerDatas) {
                // 将新的playerData合并到缓存的数据当中
                let playerData = {...lastGameData.playerDatas[playerId], ...json.playerDatas[playerId]};
                playerData.id = playerId;
                // 特殊处理playedCards
                playerData.playedCards = (lastGameData.playerDatas[playerId].playedCards||[]).concat(json.playerDatas[playerId].playedCards||[]);
                gameData.playerDatas[playerId] = playerData
            }
            lastGameData = gameData
        } else { // 全量更新
            gameData = lastGameData = json;
        }
        // 渲染数据
        for (let playerId in gameData.playerDatas) {
            let playerData = gameData.playerDatas[playerId]
            playerData.id = playerId;
            let wrapper = document.querySelector('#'+playerId);
            if (!wrapper) { // 找不到playerId的div，则创建一个
                wrapper = document.createElement('div');
                wrapper.id = playerId;
                wrapper.classList = ['playerTable'];
                document.querySelector('.chudadiTable').append(wrapper);
            }
            renderPlayerData(wrapper, playerData, playerId==currentPlayerId, playerId);
        }
    }

    function login(playerId) {
        document.title = playerId + '-锄大地'
        let msg = JSON.stringify({playerId});
        console.log('玩家登陆: ', msg)
        socket.emit('login', msg);
    }

    // 将玩家数据渲染到wrapper里（会清掉wrapper内已有的dom）
    function renderPlayerData(wrapper, playerData, canPlayCard, playerId) {
        let str = "";
        // TODO: player information (headImage nickname etc.)
        str += renderPlayerInfo(playerData);
        // played cards
        str += renderCardGroup('playedCards', (playerData.playedCards||[]).flat());
        str += '<div class="wrapper">';
            // actionDetail
            str += renderCardGroup(playerData.id + '_actionDetail', []);
            // playingCards
            str += renderCardGroup('playingCards', (playerData.playingCards||[]).flat());
            // action
            str += renderActionCode(playerData.id, playerData.actionCode);
        str += '</div>';
        str += '<div class="wrapper">';
            // TODO: 这里只处理了card，未处理from信息。数据结构：{card:[n,n,n],from:"xxxx"}
            // (playerData.groupCards||[]).forEach(gc => {
            //     let cards = gc.card;
            //     // TODO: 这里应详细区分各种杠
            //     if (gc.actionCode&ACTION_CODE.Gang) cards = Array(4).fill(gc.card);
            //     if (gc.actionCode&ACTION_CODE.Peng) cards = Array(3).fill(gc.card);
            //     str += renderCardGroup('groupCards', cards);
            // });
            // hand cards
            str += renderCardGroup('handCards', playerData.handCards, 0, playerId); // canPlayCard ? 0 : undefined
            // new card
            // if (playerData.newCard) {
            //     str += renderCardGroup('newCard', playerData.newCard, playerData.handCards.length);
            // }
        str += '</div>';
        $(wrapper).html(str);
    }

    // 渲染一张牌
    function renderCard(number, index, playerId) {
        const cardId = playerId + '-' + index
        return `
            <div class="card ${cardId}" ${index==undefined ? "" : `onclick="selectCard('${cardId}')" style="background:${type2color[number2type(number)]}"`}>
                <div class="text">${number2text(number)}</div>
            </div>
        `;
    }

    // 渲染一组牌
    function renderCardGroup(id, cards, starti, playerId) {
        cards = [].concat(cards);
        return `
            <div id="${id}-${playerId}" class="card-group">
                ${cards.map((n, i) => renderCard(n, starti==undefined ? undefined : starti+i, playerId)).join('')}
            </div>
        `;
    }

    // 数字到文字的映射
    const type2text = {0:"♦️", 1:"♣️", 2:"♥️", 3:"♠️", 9:""};
    const card2text = {1:"A", 11:"J", 12:"Q", 13:"K", 99:" "};
    function number2text(number) {
        let type = type2text[number2type(number)]
        let card = ~~(number / 10)
        
        return (card2text[card] || card) + type;
    }
    // 颜色
    const type2color = {0:"#eef", 1:"#efe", 2:"#fee", 3:"#eee"}

    // 数字映射到牌类的序号（万：0，条：1，筒：2，风：3，箭：4，卡背：5）
    function number2type(number) {
        return number % 10
    }

    // actionCode 映射 后端接受的api名
    const actionCode2actionApi = {1:"pass", 2:"followCard", 4:"playCard"};
    const actionCode2text = {1:"过", 2:"跟牌", 4:"打牌"};
    function renderActionCode(playerId, actionCode) {
        let actionArray = [];
        // 解析出蕴含的actionCode
        for (let ac in actionCode2text) (actionCode&(ac*1))!=0 && actionArray.push(actionCode&(ac*1));
        actionArray.reverse();
        return `
            <div id="${playerId}_actionCodeGroup" class="action-group">
                ${actionArray.map(ac => renderAction(playerId, ac)).join('')}
            </div>
        `;
    }

    // 渲染动作
    function renderAction(playerId, actionCode) {
        let actionText = '', onclick = '';
        // TODO: 点击事件
        onclick = `doAction('${playerId}',${actionCode},'')`;
        // 找到actionCode对应的文本
        for (let ac in actionCode2text) {
            if ((actionCode&(ac*1))!=0) {
                actionText = actionCode2text[ac];
                break;
            }
        }
        return `
            <div class="action" onclick="${onclick}"><span class="action-text">${actionText}</span></div>
        `;
    }

    // 渲染玩家信息
    function renderPlayerInfo(playerData) {
        return `
            <div class="wrapper" ${currentPlayerId==playerData.id?'style="font-weight:bold;color:#f00"':''}>${playerData.id}</div>
        `;
    }

    // 打牌动作请求
    function playCard() {
        if (userName != currentPlayerId) {
            console.log('非当前玩家，不能打牌');
            return false;
        }
        // 获取打哪些牌
        const handCards = document.querySelector(`#handCards-${userName}`).children
        const selectedCardIndexes = []
        for (let i = 0; i < handCards.length; i++) {
            // 简单认为margin-top有值即是被选中了
            if (handCards[i].style.marginTop) {
                selectedCardIndexes.push(i)
            }
        }
        // TODO: 应该在前端做一次能不能打牌的检测
        // 先简单检测是否有牌
        if (handCards.length === 0) {
            console.log('请至少选择一张牌')
            return false;
        }
        let msg = JSON.stringify({playerId: userName, action: 'playCard', data: selectedCardIndexes});
        console.log('发送信息: ', msg)
        socket.emit('chat message', msg);
    }
    // 跟牌（其实和打牌一样）
    function followCard() {
        if (userName === currentPlayerId) {
            console.log('当前玩家，不能跟牌');
            return false;
        }
        // 获取打哪些牌
        const handCards = document.querySelector(`#handCards-${userName}`).children
        const selectedCardIndexes = []
        for (let i = 0; i < handCards.length; i++) {
            // 简单认为margin-top有值即是被选中了
            if (handCards[i].style.marginTop) {
                selectedCardIndexes.push(i)
            }
        }
        // TODO: 应该在前端做一次能不能打牌的检测
        // 先简单检测是否有牌
        if (handCards.length === 0) {
            console.log('请至少选择一张牌')
            return false;
        }
        let msg = JSON.stringify({playerId: userName, action: 'followCard', data: selectedCardIndexes});
        console.log('发送信息: ', msg)
        socket.emit('chat message', msg);
    }

    // 选择一张牌(选中/取消选中)
    function selectCard(cardId) {
        const cardDom = document.querySelector('.' + cardId)
        cardDom.style.marginTop = cardDom.style.marginTop ? '' : '-10px'
    }

    // 发起动作请求（不包括打牌）
    function doAction(playerId, actionCode, data) {
        if (actionCode === ACTION_CODE.Play) {
            playCard()
        } else if (actionCode === ACTION_CODE.Follow) {
            followCard()
        } else {
            let msg = JSON.stringify({playerId, action:actionCode2actionApi[actionCode], data});
            console.log('发送信息: ', msg)
            socket.emit('chat message', msg);
        }
    }

    // 展示吃/杠选择
    function showActionDetail(playerId, actionCode) {
        let playerData = gameData.playerDatas[playerId];
        let content = '';
        if (actionCode&ACTION_CODE.Chi) content = renderChiList(playerId, playerData.chiList);
        if (actionCode&ACTION_CODE.Gang) content = renderGangList(playerId, playerData.gangList);
        $('#'+playerId+'_actionDetail').html(content);
    }
    // 渲染吃的选项
    function renderChiList(playerId, chiList) {
        return chiList.map((c, i) => `<div class="action-detail-group-wrapper"><div class="clickLayer" onclick="doAction('${playerId}',${ACTION_CODE.Chi},${i})"></div>${renderCardGroup(`chi_${i}`, c)}</div>`);
    }
    // 渲染杠的选项
    function renderGangList(playerId, gangList) {
        return gangList.map((c, i) => `<div class="action-detail-group-wrapper"><div class="clickLayer" onclick="doAction('${playerId}',${ACTION_CODE.Gang},${i})"></div>${renderCardGroup(`gang_${i}`, c)}</div>`);
    }

    // 播放回放
    async function playRecord(id) {
        if (!recordMap[id]) {
            const res = await http_get('rd/get_record', {id})
            recordMap[id] = await res.json()
        }
        console.log('开始回放' + id)
        reset()
        for (const state of recordMap[id].stateHistory) {
            onNews(JSON.stringify(state))
            await new Promise(resolve => setTimeout(resolve, 1500))
        }
    }
    // http
    // json => url params
    function json2params(query) {
        if (!query) return ''
        return '?' + encodeURI(Object.keys(query).filter(key => query[key]).map(key => `${key}=${query[key]}`).join('&'))
    }
    async function http_get(url, query) {
        return fetch(url + json2params(query))
    }
</script>
</html>